package com.test.huffmanserver.utils;

import java.io.*;
import java.util.*;

public class HuffmanUtils {

    private  Map<Byte,String> codes=new HashMap<>();//编码表
    private  Map<Integer,String>  specialMap=new HashMap<>();//当二进制数为0，或者为最后一位时
    private List<String> preOrder=new ArrayList<>();//储存树的先序序列
    private byte[] byteArr;

    public void zipFile(String fileName) {
        File originalFile=new File(FileUtils.Original+fileName);
        File zipFile=new File(FileUtils.ZipPath+fileName);
        try {
            InputStream in=new FileInputStream(originalFile);
            byte[] bytes=new byte[in.available()];
            in.read(bytes);
            in.close();

            String data=new String(bytes);
            byte[] res=zip(data);

            ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(zipFile));

            out.writeObject(res);//以对象形式写入文件
            out.writeObject(codes);
            out.writeObject(specialMap);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void unZipFile(String fileName){
        File file =new File(FileUtils.Original+fileName);
        File unzipFile=new File(FileUtils.UnZipPath+fileName);
        try {

            ObjectInputStream in=new ObjectInputStream(new FileInputStream(file));
            byte[] zipContent=(byte[])in.readObject();
            codes= (Map<Byte, String>) in.readObject();
            specialMap=(Map<Integer,String>)in.readObject();

            byte[] originalContent=unZip(zipContent);
            OutputStream out=new FileOutputStream(unzipFile);
            out.write(originalContent);
            out.close();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public byte[] zip(String originalData){

        List<Node> list=createWeight(originalData);//获取频度列表
        Node root=createHuffmanTree(list);//创建赫夫曼树
        preOrder(root);
        getCodes(root,"",new StringBuilder());//创建编码表

        return this.byteArr=Zip(StringToBytes(originalData),codes);//根据编码表压缩原始数据
    }
    public byte[] unZip(byte[] zipCodes){
        String complement=decode(zipCodes);//获取补码串
        Map<String,Byte> reCodes=reflect(codes);//反转编码表
        return reStore(reCodes,complement);
    }

    public String unZip(String complement){
        Map<String,Byte> reCodes=reflect(codes);//反转编码表
        return new String(reStore(reCodes,complement));
    }


    private byte[] StringToBytes(String str){
        byte[] res=new byte[str.length()];
        for (int i=0;i<str.length();i++){
            res[i]=(byte)str.charAt(i);
        }
        return res;
    }
    private Node createHuffmanTree(List<Node> list){//构建赫夫曼编码树
        if (list.size()>0){
            while (list.size()>1){
                Collections.sort(list);

                Node left=list.remove(0);
                Node right=list.remove(0);

                Node root= new Node(left.val + right.val, left, right);

                list.add(root);
            }
            return list.get(0);
        }else{
            return null;
        }
    }
    private List<Node> createWeight(String originalContent){//通过每个字符出现次数构建权值列表
        List<Node> list=new ArrayList<>();
        Map<Character,Integer> map=new HashMap<>();
        for(int i=0;i<originalContent.length();i++){
            char ch=originalContent.charAt(i);
            if (map.containsKey(ch))
                map.replace(ch,map.get(ch)+1);
            else
                map.put(ch,1);
        }

        for(Map.Entry<Character,Integer> entry:map.entrySet()){
            list.add(new Node(entry.getValue(), entry.getKey()));
        }
        return list;
    }
    private void getCodes(Node node,String code,StringBuilder builder){//通过赫夫曼编码树构建赫夫曼编码表
        StringBuilder temp=new StringBuilder(builder);
        temp.append(code);
        if (node!=null){
            if (node.ch==null){
                getCodes(node.left,"0",temp);
                getCodes(node.right,"1",temp);
            }else {
                codes.put((byte)((char)(node.ch)),temp.toString());
            }
        }
    }
    private byte[] Zip(byte[] originalBytes, Map<Byte, String> codes){//通过原始数据的byte数组结合编码表构建编码串，再将其压缩成byte[]
        StringBuilder processCodes=new StringBuilder();
        for(byte b:originalBytes){
            processCodes.append(codes.get(b));
        }
        int len=(processCodes.length()+7)/8;//计算byte数组的长度,此时得到的是补码串，每8位取一个。

        byte[] res=new byte[len];

        int index=0;

        for(int i=0;i<processCodes.length();i+=8){
            String complement;

            if (i+8>processCodes.length()){
                complement = processCodes.substring(i);
            }else {
                complement = processCodes.substring(i,i+8);
            }

            res[index]=(byte)Integer.parseInt(complement,2);//将补码转换成对应的byte类型
            if (res[index]==0||i+8>processCodes.length()){//单独处理
                specialMap.put(index,complement);
            }
            index++;
        }

        return res;
    }
    private byte[] reStore(Map<String,Byte> recodes,String complement){//通过补码得到原数据
        StringBuilder temp=new StringBuilder();
        List<Byte> list=new ArrayList<>();
        int index=0;
        while (index<complement.length()){
            temp.append(complement.charAt(index));
            if (recodes.containsKey(temp.toString())){
                list.add(recodes.get(temp.toString()));
                temp.delete(0,temp.length());
            }
            index++;
        }
        byte[] res=new byte[list.size()];
        for(int i=0;i<list.size();i++)
            res[i]=list.get(i);
        return res;
    }
    private Map<String,Byte> reflect(Map<Byte, String> codes){//将编码表反转
        Map<String,Byte> reCodes=new HashMap<>();
        for (Map.Entry<Byte, String> entry:codes.entrySet())
            reCodes.put(entry.getValue(), entry.getKey());
        return reCodes;
    }

    private String decode(byte[] zipCodes){//根据bytes数组获取补码
        StringBuilder builder=new StringBuilder();
        for(int i=0;i<zipCodes.length;i++){
            if (specialMap.containsKey(i)){//如果特殊映射里含有此值，则优先使用特殊映射里的val
                builder.append(specialMap.get(i));
            }else {
                builder.append(toBinary(i+1<zipCodes.length,zipCodes[i]));
            }
        }
        return builder.toString();
    }

    private String toBinary(boolean isNeed,byte b){
        int intNum=b;
        if (isNeed)
            intNum |=256;
        String res=Integer.toBinaryString(intNum);
        if (isNeed)
            return res.substring(res.length()-8);
        else
            return res;
    }
    public List<Byte> arrayToList(byte[] array){
        List<Byte> list=new ArrayList<>();
        for(byte b:array)
            list.add(b);
        return list;
    }

    public byte[] listToArray(List<Byte> list){
        byte[] arr=new byte[list.size()];
        for(int i=0;i<arr.length;i++)
            arr[i]=list.get(i);
        return arr;
    }

    public void preOrder(Node node){
        if (node==null)
            return;

        preOrder.add(String.valueOf(node.val));

        if (node.left!=null)
            preOrder(node.left);
        else
            preOrder.add("#");

        if (node.right!=null)
            preOrder(node.right);
       else
            preOrder.add("#");
    }

    public Map<Byte, String> getCodes() {
        return codes;
    }

    public void setCodes(Map<Byte, String> codes) {
        this.codes = codes;
    }

    public Map<Integer, String> getSpecialMap() {
        return specialMap;
    }

    public void setSpecialMap(Map<Integer, String> specialMap) {
        this.specialMap = specialMap;
    }

    public List<String> getPreOrder() {
        return preOrder;
    }

    public void setPreOrder(List<String> preOrder) {
        this.preOrder = preOrder;
    }

    public byte[] getByteArr() {
        return byteArr;
    }

    public void setByteArr(byte[] byteArr) {
        this.byteArr = byteArr;
    }

    private static class Node implements Comparable<Node>{
        int val;
        Character ch=null;
        Node left;
        Node right;

        public Node(int val,char ch){
            this.val=val;
            this.ch=ch;
        }

        public Node(int val,Node left,Node right){
            this.val=val;
            this.left=left;
            this.right=right;
        }

        @Override
        public int compareTo(Node o) {
            return this.val-o.val;
        }

        public String toString(){
            return this.ch+":"+this.val;
        }
    }
}